/*
 * Porting to u-boot:
 *
 * (C) Copyright 2019
 * TangHaifeng <tang-haifeng@foxmail.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <linux/list.h>
#include <linux/fb.h>
#include <video_bridge.h>
#include <dm.h>
#include <i2c.h>
#include <edid.h>

#include <asm/io.h>
#include <asm/gpio.h>

#if defined(CONFIG_MACH_LOONGSON1)
#include <mach/ls1x.h>
#elif defined(CONFIG_MACH_LOONGSON2_SOC) || defined(CONFIG_MACH_LOONGARCH_LOONGSON2)
#include <mach/loongson.h>
#elif defined(CONFIG_SB_LS7A1000)
#include <mach/ls7a.h>
#endif

#if defined(CONFIG_VIDEO) || defined(CONFIG_DM_VIDEO)
#include <ls1xfb.h>

enum panel_dev {
	PANEL_DEV_UNKNOWN = 0,
	PANEL_VESA800X600_60 = 8,
	PANEL_DEV_COUNT,			/* Number of dev types */
};

struct fb_videomode panel[] = {
	{
	.name	= "at043tn13",
	.pixclock	= 111000,
	.refresh	= 60,
	.xres		= 480,
	.yres		= 272,
	.hsync_len	= 41,		// 523-482
	.left_margin	= 2,	// 525-523
	.right_margin	= 2,	// 482-480
	.vsync_len	= 10,		// 284-274
	.upper_margin	= 4,	// 288-284
	.lower_margin	= 2,	// 274-272
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "lcms034va07",
	.pixclock	= 64100,
	.refresh	= 60,
	.xres		= 480,
	.yres		= 480,
	.hsync_len	= 28,
	.left_margin	= 2,
	.right_margin	= 2,
	.vsync_len	= 22,
	.upper_margin	= 4,
	.lower_margin	= 2,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "tl2970",
	.pixclock	= 34209,
	.refresh	= 60,
	.xres		= 480,
	.yres		= 800,
	.hsync_len	= 10,
	.left_margin	= 10,
	.right_margin	= 10,
	.vsync_len	= 3,
	.upper_margin	= 10,
	.lower_margin	= 10,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "g065vn01",	//SR-G065
	.pixclock	= 39700,
	.refresh	= 60,
	.xres		= 640,
	.yres		= 480,
	.hsync_len	= 96,
	.left_margin	= 40,
	.right_margin	= 8,
	.vsync_len	= 2,
	.upper_margin	= 25,
	.lower_margin	= 2,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "at070tn93",	// AT070TN93
	.pixclock	= 34209,
	.refresh	= 60,
	.xres		= 800,
	.yres		= 480,
	.hsync_len	= 48,		// 888-840
	.left_margin	= 40,	// 928-888
	.right_margin	= 40,	// 840-800
	.vsync_len	= 3,		// 496-493
	.upper_margin	= 29,	// 525-496
	.lower_margin	= 13,	// 493-480
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "hk070r21b01",
	.pixclock	= 19531,
	.refresh	= 60,
	.xres		= 1024,
	.yres		= 600,
	.hsync_len	= 192,
	.left_margin	= 80,
	.right_margin	= 48,
	.vsync_len	= 7,
	.upper_margin	= 14,
	.lower_margin	= 3,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "hz101wx",
	.pixclock	= 14084,
	.refresh	= 60,
	.xres		= 1280,
	.yres		= 800,
	.hsync_len	= 32,
	.left_margin	= 80,
	.right_margin	= 48,
	.vsync_len	= 6,
	.upper_margin	= 14,
	.lower_margin	= 3,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{
	.name	= "b133xw01",
	.pixclock	= 11696,
	.refresh	= 60,
	.xres		= 1366,
	.yres		= 768,
	.hsync_len	= 143,
	.left_margin	= 213,
	.right_margin	= 70,
	.vsync_len	= 3,
	.upper_margin	= 24,
	.lower_margin	= 3,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 800x600@60Hz
	.name	= "vesa800x600@60",
	.pixclock	= 25000,
	.refresh	= 60,
	.xres		= 800,
	.yres		= 600,
	.hsync_len	= 128,		// 896-816
	.left_margin	= 88,	// 1056-896
	.right_margin	= 40,	// 816-800
	.vsync_len	= 4,		// 484-481
	.upper_margin	= 23,	// 500-484
	.lower_margin	= 1,	// 481-480
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 800x600@75Hz
	.name	= "vesa800x600@75",
	.pixclock	= 20202,
	.refresh	= 75,
	.xres		= 800,
	.yres		= 600,
	.hsync_len	= 80,		// 896-816
	.left_margin	= 160,	// 1056-896
	.right_margin	= 16,	// 816-800
	.vsync_len	= 3,		// 484-481
	.upper_margin	= 21,	// 500-484
	.lower_margin	= 1,	// 481-480
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1024x768@60Hz
	.name	= "vesa1024x768@60",
	.pixclock	= 15384,
	.refresh	= 60,
	.xres		= 1024,
	.yres		= 768,
	.hsync_len	= 136,		// 896-816
	.left_margin	= 160,	// 1056-896
	.right_margin	= 24,	// 816-800
	.vsync_len	= 6,		// 484-481
	.upper_margin	= 29,	// 500-484
	.lower_margin	= 3,	// 481-480
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1280x720@60Hz
	.name	= "vesa1280x720@60",
	.pixclock	= 13468,
	.refresh	= 60,
	.xres		= 1280,
	.yres		= 720,
	.hsync_len	= 40,
	.left_margin	= 220,
	.right_margin	= 110,
	.vsync_len	= 5,
	.upper_margin	= 20,
	.lower_margin	= 5,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1280x1024@60Hz
	.name	= "vesa1280x1024@60",
	.pixclock	= 9259,
	.refresh	= 60,
	.xres		= 1280,
	.yres		= 1024,
	.hsync_len	= 112,
	.left_margin	= 248,
	.right_margin	= 48,
	.vsync_len	= 3,
	.upper_margin	= 38,
	.lower_margin	= 1,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1280x1024@75Hz
	.name	= "vesa1280x1024@75",
	.pixclock	= 7400,
	.refresh	= 75,
	.xres		= 1280,
	.yres		= 1024,
	.hsync_len	= 144,
	.left_margin	= 248,
	.right_margin	= 16,
	.vsync_len	= 3,
	.upper_margin	= 38,
	.lower_margin	= 1,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1440x900@75Hz
	.name	= "vesa1400x900@75",
	.pixclock	= 7312,
	.refresh	= 75,
	.xres		= 1440,
	.yres		= 900,
	.hsync_len	= 152,
	.left_margin	= 248,
	.right_margin	= 96,
	.vsync_len	= 6,
	.upper_margin	= 33,
	.lower_margin	= 3,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1920x1080@60Hz
	.name	= "vesa1920x1080@60",
	.pixclock	= 6734,
	.refresh	= 60,
	.xres		= 1920,
	.yres		= 1080,
	.hsync_len	= 44,
	.left_margin	= 148,
	.right_margin	= 88,
	.vsync_len	= 5,
	.upper_margin	= 36,
	.lower_margin	= 4,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
	{	//VESA 1920x1200@60Hz
	.name	= "vesa1920x1200@60",
	.pixclock	= 5175,
	.refresh	= 60,
	.xres		= 1920,
	.yres		= 1200,
	.hsync_len	= 200,
	.left_margin	= 336,
	.right_margin	= 136,
	.vsync_len	= 6,
	.upper_margin	= 36,
	.lower_margin	= 3,
	.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
	},
};

struct ls1xfb_mach_info ls1x_lcd_info[] = {
	[0] = {
	.id			= "Graphic 0",
	.modes			= (struct fb_videomode *)&panel[PANEL_VESA800X600_60],
	.pix_fmt		= PIX_FMT_RGB565,
	.de_mode		= 0,	/* 注意：lcd是否使用DE模式 */
	/* 根据lcd屏修改invert_pixclock和invert_pixde参数(0或1)，部分lcd可能显示不正常 */
	.invert_pixclock	= 0,
	.invert_pixde	= 0,
	},
	[1] = {
	.id			= "Graphic 1",
	.modes			= (struct fb_videomode *)&panel[PANEL_VESA800X600_60],
	.pix_fmt		= PIX_FMT_RGB565,
	.de_mode		= 0,	/* 注意：lcd是否使用DE模式 */
	/* 根据lcd屏修改invert_pixclock和invert_pixde参数(0或1)，部分lcd可能显示不正常 */
	.invert_pixclock	= 0,
	.invert_pixde	= 0,
	},
};

int panel_count = ARRAY_SIZE(panel);

static struct fb_videomode *ls1x_videomode(char const *e)
{
	struct fb_videomode *videomode;
	int dev;

	for (dev = 0; dev <= panel_count; dev++) {
		videomode = (struct fb_videomode *)&panel[dev];
		if (videomode->name && !strcmp(e, videomode->name)) {
			break;
		}
	}

	if (dev > panel_count) {
		videomode = (struct fb_videomode *)&panel[PANEL_VESA800X600_60];
	}

	return videomode;
}

static void loongson_dvo_init(void)
{
	int __maybe_unused val;
#if defined(LS1X_DC0)
#ifdef CONFIG_CPU_LOONGSON2K1000
	#if defined(CONFIG_VIDEO)
	//设置dvo0复用
	//lio_set = 0
	val = readl((unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE));
	val = val & (~(1<<7));
	writel(val, (unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE));
	//uart2_del = 0 uart1_del = 0
	val = readl((unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x08));
	val = val & (~(1<<13 | 1<<12));
	writel(val, (unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x08));
	//dvo0_set = 1
	val = readl((unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x10));
	val = val | (1<<1);
	writel(val, (unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x10));
	#endif
#endif
#endif

#if defined(LS1X_DC1)
#ifdef CONFIG_CPU_LOONGSON2K1000
	#if defined(CONFIG_VIDEO)
	//cam_sel = 0 dvo1_set = 1
	val = readl((unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x10));
	val = val & (~(1<<5));
	val = val | (1<<4);
	writel(val, (unsigned int *)CKSEG1ADDR(LS2X_MUX_BASE + 0x10));
	#endif
#endif
#endif
}

#if defined(CONFIG_I2C_EDID)
struct fb_videomode edid_mode;
char manufacturer[4];

static void edid_get_manufacturer_name(struct edid1_info *edid, char *name)
{
	name[0] = EDID1_INFO_MANUFACTURER_NAME_CHAR1(*edid) + 'A' - 1;
	name[1] = EDID1_INFO_MANUFACTURER_NAME_CHAR2(*edid) + 'A' - 1;
	name[2] = EDID1_INFO_MANUFACTURER_NAME_CHAR3(*edid) + 'A' - 1;
	name[3] = '\0';
}

static void edid_get_timing_to_mode(struct edid1_info *edid)
{
    struct display_timing timing;
	int panel_bits_per_colour;

//	edid_print_info(edid);

	// fix edid video_input_definition
	// set it to digital input for edid_get_timing can decode it.
	edid->video_input_definition |= (1 << 7);

	edid_get_timing((u8 *)edid, EDID_SIZE, &timing, &panel_bits_per_colour);

	debug("display timing:\npixclk: %u, "
			"hdisp: %u, hfp: %u, hbp: %u, hsynclen: %u\n"
			"vdisp: %u, vfp: %u, vbp: %u, vsynclen: %u\n"
			"flag: 0x%x\n",
			timing.pixelclock.typ,
			timing.hactive.typ,
			timing.hfront_porch.typ,
			timing.hback_porch.typ,
			timing.hsync_len.typ,
			timing.vactive.typ,
			timing.vfront_porch.typ,
			timing.vback_porch.typ,
			timing.vsync_len.typ,
			timing.flags);

	edid_get_manufacturer_name(edid, manufacturer);

	edid_mode.name = manufacturer;
	edid_mode.pixclock	= KHZ2PICOS(timing.pixelclock.typ / 1000);
	edid_mode.refresh	= 60;
	edid_mode.xres		= timing.hactive.typ;
	edid_mode.yres		= timing.vactive.typ;
	edid_mode.hsync_len	= timing.hsync_len.typ;
	edid_mode.left_margin	= timing.hback_porch.typ;
	edid_mode.right_margin	= timing.hfront_porch.typ;
	edid_mode.vsync_len	= timing.vsync_len.typ;
	edid_mode.upper_margin	= timing.vback_porch.typ;
	edid_mode.lower_margin	= timing.vfront_porch.typ;
	edid_mode.sync		= FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT;
}
#endif

#if CONFIG_IS_ENABLED(DM_VIDEO)
int loongson_displays_init(struct udevice *dev, int id)
#else
int board_video_skip(void)
#endif
{
	struct ls1xfb_mach_info *info;
	struct fb_videomode *videomode = NULL;
	char *e = NULL;
	pci_dev_t __maybe_unused busdevfunc;
	void __iomem *iobase;
#ifdef LOONGSON_USED_GMEM
	ulong gmem = 0;
#endif
	char buf[16];

	loongson_dvo_init();
	info = &ls1x_lcd_info[id];

#if defined(CONFIG_MACH_LOONGSON1)
	iobase = ioremap(LS1X_DC_BASE, 1);
#elif defined(CONFIG_CPU_LOONGSON2K1000) || defined(CONFIG_SB_LS7A1000)
	busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC, 0); /* get PCI Device ID */
	if (busdevfunc == -1) {
		printf("DC Controller (%04X,%04X) not found\n", PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC);
		return 0;
	}
	iobase = pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_0, PCI_REGION_MEM);

	#ifdef LOONGSON_USED_GMEM
	busdevfunc = pci_find_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU, 0); /* get PCI Device ID */
	if (busdevfunc == -1) {
		printf("GPU Controller (%04X,%04X) not found\n", PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU);
		return 0;
	}
	#if defined(CONFIG_MIPS)
	pci_read_config32(busdevfunc, PCI_BASE_ADDRESS_2, (u32 *)&gmem);
	gmem &= PCI_BASE_ADDRESS_MEM_MASK;
	gmem = pci_mem_to_phys(busdevfunc, gmem);
	#elif defined(CONFIG_LOONGARCH)
	gmem = (ulong)pci_map_bar(busdevfunc, PCI_BASE_ADDRESS_2, PCI_REGION_MEM);
	#endif
	info->gmem = (void *)gmem;
	#endif //LOONGSON_USED_GMEM
#elif defined(CONFIG_CPU_LOONGSON2K500) || defined(CONFIG_CPU_LOONGSON2H)
	iobase = ioremap(LS2X_DC_BASE, 1);
#endif //CONFIG_MACH_LOONGSON1

	info->base = (void *)(iobase + 0x1240 + id * 0x10);
	if (id) {
	#if defined(LS1X_DC1_USE_DEMODE)
		info->de_mode = 1;
	#endif
	} else {
	#if defined(LS1X_DC0_USE_DEMODE)
		info->de_mode = 1;
	#endif
	}

	dev_read_u32(dev, "dvo,invert_pixclock", &info->invert_pixclock);
	dev_read_u32(dev, "dvo,invert_pixde", &info->invert_pixde);

#if defined(CONFIG_DM_VIDEO) && defined(CONFIG_I2C_EDID)
	{
	/* Try to get timings from bridge first */
	struct udevice *cdev;
	struct edid1_info edid;
	int ret = 0;

	memset((void *)&edid_mode, 0, sizeof(edid_mode));
	memset((void *)&edid, 0, sizeof(edid));

	ret = uclass_get_device_by_phandle(UCLASS_I2C, dev, "ddc-i2c-bus", &cdev);
	if (!ret) {
		struct udevice *chip;
		ret = i2c_get_chip(cdev, 0x50, 1, &chip);
		if (ret) {
			goto get_bridge;
		}
		ret = dm_i2c_read(chip, 0, (u8 *)&edid, sizeof(edid));
		if (ret) {
			debug("video edid get failed: %d\n", ret);
			goto get_bridge;
		}
		if (edid_check_info(&edid)) {
			debug("not a valid edid\n");
			goto get_bridge;
		}
		edid_get_timing_to_mode(&edid);
	}

get_bridge:;
	#if defined(CONFIG_VIDEO_BRIDGE)
	ret = uclass_get_device_by_phandle(UCLASS_VIDEO_BRIDGE, dev, "bridge", &cdev);
	if (ret) {
		goto get_panel;
	}
	ret = video_bridge_attach(cdev);
	if (ret) {
		debug("video bridge attach failed: %d\n", ret);
		goto get_panel;
	}
	ret = video_bridge_read_edid(cdev, (u8 *)&edid, EDID_SIZE);
	if (ret > 0) {
		edid_get_timing_to_mode(&edid);
	}
get_panel:;
	#endif
	}
#endif

	sprintf(buf, "panel%d", id);
	e = env_get(buf);
	if (e == NULL)
		e = env_get("panel");
	if (e != NULL) {
		videomode = ls1x_videomode(e);
	}
#if defined(CONFIG_I2C_EDID)
	if (edid_mode.name) {
		videomode = &edid_mode;
		if (edid_mode.xres > 2560 || edid_mode.yres > 1600) {
			printf("DC%d Edid timing overstep the loongson cpu's dc range, used VESA800X600@60Hz instead\r\n", id);
			videomode = (struct fb_videomode *)&panel[PANEL_VESA800X600_60];
		}
	}
#endif
	if (videomode == NULL) {
		printf("DC%d cannot be configured, please setenv panel%d, used VESA800X600@60Hz instead\r\n", id, id);
		videomode = (struct fb_videomode *)&panel[PANEL_VESA800X600_60];
	}

	ls1x_fb_init(videomode, id, info);

	return 0;
}

/* no console on this board */
#if defined(CONFIG_VIDEO_BMP_LOGO)
int board_cfb_skip(void)
{
#if 0
	int ret;
	run_command("sf probe", 0);
	run_command("mtd read uboot_logo 0x80100000 0", 0);
//	run_command("mtd read logo 0x80100000 0", 0);
	ret = run_command("bmp info 0x80100000", 0);
	if (!ret) {
		run_command("bmp display 0x80100000 m m", 0);
	}
#endif
	return 1;
}
#endif

#endif //defined(CONFIG_VIDEO) || defined(CONFIG_DM_VIDEO)
